package se.kth.ik2200.carenet_2012;

import javax.bluetooth.RemoteDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 * User: Ruslan Mukhammadov
 * Date: November 12, 2012
 * Time: 11:18 PM
 * To change this template use File | Settings | File Templates.
 */

/**
 * Zephyr BioHarness sensor is based on simple request/response
 * format. First, PC initiates a connection with BioHarness by
 * sending specific data format. Zephyr BioHarness supports few
 * data formats:
 * 1. General Data Packet. Transmits low resolution data:
 *      a) Heart Rate
 *      b) Skin Temperature
 *      c) Breathing Rate
 *    Transmission period is 1000 ms.
 *
 * 2. Breathing Waveform. Sampled breathing waveform with a
 *    resolution of 56ms. Transmission period is 1008 ms.
 *
 * 3. R to R Data. Sampled R to R data calculated from the ECG
 *    with a resolution of 4ms.Transmission period is 1008 ms.
 *
 * 4. ECG Waveform. Sampled ECG waveform with a resolution of 4ms.
 *    Transmission period is 252 ms.
 *
 * 5. Accelerometer Waveforms. Sampled X, Y & Z accelerometer waveforms
 *    with a resolution of 20ms. Transmission period is 400 ms.
 */
@SuppressWarnings("FieldCanBeLocal")
public class ZephyrBioHarness extends Device {

    public static final String DEVICE_MODEL = "Zephyr BioHarness BT";

    /** End of text */
    private final byte ETX = 0x03;

    /**
     * Normal packet size in general data packet format
     * is 58 bytes.
     */
    private final int GENERAL_DATA_PACKET_LENGTH = 58;

    /** Start of text */
    private final byte STX = 0x02;



    private RemoteDevice remoteDevice;

    /**
     * If the number of empty packets reach to 10,
     * we have to stop receiving and parsing them.
     */
    private int emptyPacketCounter = 0;

    @Override
    public String getDeviceDescription() {
        return "BioHarness BT couples the pioneering Smart Fabric sensor technology of the comfortable and " +
                "unobtrusive BioHarness garment with the power and ubiquity of Bluetooth®. BioHarness BT enables " +
                "the capture and transmission of comprehensive physiological data on the wearer via mobile and fixed " +
                "data networks by enabling genuine remote monitoring of human performance and condition in the real world.";
    }

    @Override
    public String getDeviceModel() {
        return DEVICE_MODEL;
    }

    @Override
    public String getDeviceName() throws IOException {
        return extractDeviceName(remoteDevice);
    }

    /**
     * Usually all Zephyr BioHarness Devices have the same pin code.
     *
     * @return - returns the pin code of the device
     */
    @Override
    public String getDevicePinCode() {
        return "1234";
    }

    @Override
    public RemoteDevice getInstance() {
        return remoteDevice;
    }

    @Override
    public void readDevice(String service, String username)
            throws InterruptedException, IOException, NoSuchAlgorithmException, KeyManagementException {
        /**
         * RFCOMM connection should be initiated and established with Zephyr BioHarness
         * sensor to read the data. There is no need for OBEX connection.
         */
        StreamConnection rfCommConnection = (StreamConnection) Connector.open(service);

        /**
         * By default Nonin sensor uses #13 format that transmit packets
         * every 1.75 seconds.  We are changing the data format from #13
         * to #8 that sends packets every 1 second.
         */
        OutputStream outputStream = rfCommConnection.openOutputStream();

        Thread.sleep(1000);

        outputStream.write(enableGeneralDataPacketTransmission(true));
        outputStream.close();

        InputStream inputStream = rfCommConnection.openInputStream();

        /**
         * We have to receive packets from sensor continuously
         * until it stops to transmit.
         */
        while (true) {
            /**
             * In #8 format, data arrives every 1 second. So it does
             * not make sense getting the same  data again and again
             * during 1 second.Nonin sensor by default uses #13 data
             * format, however we are using another #8 data format.
             *
             * In #13 data format,Nonin sensor will transmit packets
             * every 3/4 seconds and in #8 format it will send every
             * 1 second.
             */
            Thread.sleep(1000);

            byte[] packet = getBytesFromInputStream(inputStream);

            /**
             * If the sensor stops to transmit packets, PC has to
             * stop parsing packets, because all sizes will be 0.
             */
            if (packet.length == 0) {
                emptyPacketCounter++;

                if (emptyPacketCounter == 5) {
                    Log.printTitle("Packets transmission is stopped!");

                    inputStream.close();

                    break;
                }
            }

            String date = new Date().toString();
            String log = "Received " + packet.length + " bytes in General Data Format. Date: " + date;
            Log.printOrdinaryLog(log);

            if (packet.length == GENERAL_DATA_PACKET_LENGTH) {
                parseGeneralDataPacket(packet, username);
            } else if (packet.length > 0) {
                Log.printWarning("Wrong Data Packet Received.");
            }
        }
    }

    @Override
    public void setInstance(RemoteDevice remoteDevice) {
        this.remoteDevice = remoteDevice;
    }

    /**
     * Convert a byte to a hex string.
     *
     * @param data
     *            the byte to convert
     * @return String the converted byte
     */
    public static String byteToHex(byte data) {
        StringBuilder buf = new StringBuilder();
        buf.append(toHexChar((data >>> 4) & 0x0F));
        buf.append(toHexChar(data & 0x0F));
        return buf.toString();
    }

    private byte[] enableGeneralDataPacketTransmission(boolean enable) {
        byte[] request = new byte[6];
        request[0] = STX; // Start of text
        request[1] = 0x14; //Message ID
        request[2] = 0x01; //DLC
        if (enable) {
            request[3] = 0x01; // Enabling transmission, general data packet is transmitted periodically every 1.008 seconds.
        } else {
            request[3] = 0x00; // Disabling transmission
        }
        request[4] = 0x5e; // CRC - checksum
        request[5] = ETX; // End of text
        return request;
    }

    /**
     * Merge two bytes into a signed 2's complement integer
     *
     * @param low
     *            byte is LSB
     * @param high
     *            byte is the MSB
     * @return a signed int value
     */
    public static int merge(byte low, byte high) {
        int b = 0;
        b += (high << 8) + low;
        if ((high & 0x80) != 0) {
            b = -(0xffffffff - b);
        }
        return b;
    }

    private void parseGeneralDataPacket(byte[] packet, String username)
            throws IOException, NoSuchAlgorithmException, KeyManagementException {
        String beat = parseString(packet, 3);
        Log.printf("Beat: %s", beat);

        String hrBytes = byteToHex(packet[12]);
        short hrValue = Short.parseShort(hrBytes, 16);
        String heart = Short.toString(hrValue);
        Log.printf("    Heart: %s", heart);

//        int v = merge(packet[24], packet[25]);
//        String battery = String.valueOf(((double) v / (double) 1000));
//        Log.printf("    Battery: %s", battery);

        int p = merge(packet[18], packet[19]);
        String posture = String.valueOf(((double) p / (double) 10));
        Log.printf("    Posture: %s", posture);

        int r = merge(packet[14], packet[15]);
        String respiration = String.valueOf(Math.abs(((double) r / (double) 10)));
        Log.printf("    Respiration: %s", respiration);

        int t = merge(packet[16], packet[17]);
        String temperature = String.valueOf(((double) t / (double) 10));
        Log.printf("    Temperature: %s", temperature + "\n\n");

        SensorData data = getDeviceData(username);
        data.setHeartBeat(beat);
        data.setHeartRate(heart);
        data.setTemperature(temperature);
        data.setRespiration(respiration);
        data.setPosture(posture);

        DataTransmissionHandler.sendDataToWebServer(data);
    }

    private String parseString(byte[] packet, int index) {
        String hex = byteToHex(packet[index]);
        short value = Short.parseShort(hex, 16);
        return String.valueOf(value);
    }

    /**
     * Converts an int to a hex char.
     *
     * @param i is the int to convert
     * @return char the converted char
     */
    public static char toHexChar(int i) {
        if ((0 <= i) && (i <= 9)) {
            return (char) ('0' + i);
        } else {
            return (char) ('a' + (i - 10));
        }
    }
}
